package com.acme.hashmap;

import com.acme.hash.HashConvolution;
import com.acme.hash.IntHashFunction;
import com.acme.hash.SimpleIntHashFunctionImpl;

/**
 * @author Gregory Sobko
 */
public class LinkedHashMapIntToInt implements MapIntToInt {

    public final int NULL_POINTER = -1;

    /** should be in range (0, 1] */
    private final double STORAGE_INCREASE_THRESHOLD = 1;

    /** should be in range (0, +inf). if > 1 => there definitely will be collisions */
    private final double HASHTABLE_INCREASE_THRESHOLD = 2;


    private int hashCapacity;

    private int[] hashTable;

    private int[] linkedStorage;

    private int linkedStorageCapacity ;

    private int linkedStoragePointer = 0;

    private int size = 0;

    private HashConvolution hash;


    public LinkedHashMapIntToInt(){
        this(new SimpleIntHashFunctionImpl(), 5);
    }

    public LinkedHashMapIntToInt(IntHashFunction intHashFunction) {
        this(intHashFunction, 5);
    }

    public LinkedHashMapIntToInt(IntHashFunction intHashFunction, int initialCapacity){
        linkedStorageCapacity = hashCapacity = initialCapacity;
        hash = new HashConvolution(intHashFunction);
        hash.setDimension(hashCapacity);
        initHashTable();
        initLinkedStorage();
    }

    @Override
    public Integer put(int key, int value){
        int hash = this.hash.hash(key);
        int firstIndex = hashTable[hash];
        int previous;
        if(firstIndex == NULL_POINTER){
            hashTable[hash] = putElement(linkedStoragePointer,key,value);
            incrementSize();
        }else{
            int currentIndex = firstIndex;
            int nextIndex = currentIndex;
            do{
                if(key == getElementKey(nextIndex)){
                    previous = getElementValue(nextIndex);
                    setElementValue(nextIndex, value);
                    return previous;
                }
                currentIndex = nextIndex;
            }while((nextIndex=getElementNext(nextIndex))!=NULL_POINTER);
            setElementNext(currentIndex, putElement(linkedStoragePointer, key, value));
            incrementSize();
        }
        return null;
    }

    @Override
    public Integer get(int key) {
        int hash = this.hash.hash(key);
        return getValueByKey(hashTable[hash], key);
    }

    @Override
    public boolean contains(int key) {
        return get(key) != null ;
    }

    @Override
    public Integer remove(int key) {
        return null;  //To change body of implemented methods use File | Settings | File Templates.
    }

    @Override
    public void clear() {
        initHashTable();
        initLinkedStorage();
        size=0;
        linkedStoragePointer=0;
    }

    @Override
    public int size() {
        return size;
    }

    private Integer getValueByKey(int index, int key){
        if(index == NULL_POINTER){
            return null;
        }else if(getElementKey(index) == key){
            return getElementValue(index) ;
        }else{
            return getValueByKey(getElementNext(index), key);
        }
    }


    private void incrementSize(){
        size++;
        linkedStoragePointer++;
        // 1 check linked storage
        if(size >= linkedStorageCapacity * STORAGE_INCREASE_THRESHOLD){
            increaseStorageSize(1.2);
        }
        // 2 check hashtable size
        if(size >= hashCapacity * HASHTABLE_INCREASE_THRESHOLD){
            increaseHashTableSize(1.2);
        }
        
    }

    private void increaseStorageSize(double times){
        int[] copyFrom  = linkedStorage;
        linkedStorageCapacity *= times;
        initLinkedStorage();
        System.arraycopy(copyFrom, 0, linkedStorage, 0, copyFrom.length);

    }
    private void increaseHashTableSize(double times){
        hashCapacity *= times;
        hash.setDimension(hashCapacity);
        initHashTable();
        rehash();
    }
    private void rehash(){
        clearLinkedStorageLinks();
        for (int i = 0; i < linkedStoragePointer; i++) {
            int key = getElementKey(i);
            int hash = this.hash.hash(key);
            int firstIndex = hashTable[hash];
            if(firstIndex == NULL_POINTER){
                hashTable[hash] = i;
            }else{
                int currentIndex = firstIndex;
                int nextIndex;
                while((nextIndex = getElementNext(currentIndex))!= NULL_POINTER){
                    currentIndex = nextIndex;
                }
                setElementNext(currentIndex, i);
            }
        }
    }

    public int getElementKey(int storageIndex){
        return linkedStorage[3 * storageIndex];
    }

    public int getElementValue(int storageIndex){
        return linkedStorage[3 * storageIndex + 1];
    }

    public int getElementNext(int storageIndex){
        return linkedStorage[3 * storageIndex + 2];
    }

    public void setElementKey(int storageIndex, int key){
        linkedStorage[3 * storageIndex] = key;
    }

    public void setElementValue(int storageIndex, int value){
        linkedStorage[3 * storageIndex + 1] = value;
    }

    public void setElementNext(int storageIndex, int next){
        linkedStorage[3 * storageIndex + 2] = next;
    }
    
    
    private int putElement(int storageIndex, int key, int value){
        setElementKey(storageIndex, key);
        setElementValue(storageIndex, value);
        setElementNext(storageIndex, NULL_POINTER);
        return storageIndex;
    }

    private void initHashTable(){
        hashTable = new int[hashCapacity];
        for (int i = 0; i < hashCapacity; i++) {
            hashTable[i] = NULL_POINTER;
        }
    }

    private void initLinkedStorage(){
        linkedStorage = new int[3 * linkedStorageCapacity];
        clearLinkedStorageLinks();
    }

    private void clearLinkedStorageLinks(){
        for (int i = 0; i < linkedStorage.length; i+=3) {
            linkedStorage[i + 2] = NULL_POINTER;
        }
    }

}
